/*
 * 描          述:  <描述>
 * 修  改   人:  Administrator
 * 修改时间:  2014年11月24日
 * <修改描述:>
 */
package com.tx.component.file.resource;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.NameScope;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.tx.component.file.exception.FileContextResourceException;
import com.tx.component.file.util.FCResourceFilterUtils;
import com.tx.core.exceptions.util.AssertUtils;
import com.tx.core.util.MessageUtils;

/**
 * 文件定义资源实体<br/>
 * 接口的实现可以是基于SystemFile的实现<br/>
 * 也可以是基于VFSFile的实现<br/>
 * 或者是基于其他的实现<br/>
 * 
 * @author Administrator
 * @version [版本号, 2014年11月24日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@JsonSerialize(using = FCResourceJsonSerializer.class)
public interface FCResource {
    
    /** default write buffer size */
    static final int WRITE_BUFFER_SIZE = 4096;
    
    /** default buffer size */
    static final int DEFAULT_BUFFER_SIZE = 8192;
    
    /**
     * 获取FCResource对应的虚拟目录名<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    public String getCatalog();
    
    /**
     * 获取文件存储相对路径<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    String getRelativePath();
    
    /**
     * 获取文件类型<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default String getContentType() {
        if (FCResourceTypeEnum.FILE.equals(this.getType())) {
            try {
                return Files.probeContentType(Paths.get(getRelativePath()));
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getContentType() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        } else {
            return null;
        }
    }
    
    /**
     * 获取文件名<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default String getFilename() {
        String filename = StringUtils.getFilename(getRelativePath());
        return filename;
    }
    
    /**
     * 获取文件名后缀<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default String getFilenameExtension() {
        String filenameExtension = StringUtils
                .getFilenameExtension(getRelativePath());
        return filenameExtension;
    }
    
    /**
     * 根据相对路径创建关联文件<br/>
     * <功能详细描述>
     * @param relativePath
     * @return [参数说明]
     * 
     * @return FileResource [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    FCResource createRelative(String relativePath);
    
    /**
     * Returns the folder that contains this file.
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return FCResource [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default FCResource getParent() {
        FCResource fcResource = this.createRelative("");
        return fcResource;
    }
    
    /**
     * Returns a child of this file. 
     * Note that this method returns {@code null} when the child does not exist. 
     * This differs from {@link #resolveFile(String, NameScope)} which never returns null.
     * <功能详细描述>
     * @param name
     * @return [参数说明]
     * 
     * @return FCResource [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    FCResource getChild(String name);
    
    /**
     * 获取资源类型：文件夹还是文件<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return FCResourceTypeEnum [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    FCResourceTypeEnum getType() throws FileContextResourceException;
    
    /**
     * 是否可写入<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default boolean isWriteable() throws FileContextResourceException {
        if (FCResourceTypeEnum.FILE.equals(this.getType())) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 文件是否可读取<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default boolean isReadable() throws FileContextResourceException {
        if (FCResourceTypeEnum.FILE.equals(this.getType())) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 是否是文件<br/>
     * <功能详细描述>
     * @return
     * @throws FileSystemException [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default boolean isFile() throws FileContextResourceException {
        if (FCResourceTypeEnum.FILE.equals(this.getType())) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 创建文件<br/>
     * <功能详细描述>
     * @throws FileSystemException [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    void createFile() throws FileContextResourceException;
    
    /**
     * 是否是文件夹<br/>
     * <功能详细描述>
     * @return
     * @throws FileSystemException [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default boolean isFolder() throws FileContextResourceException {
        if (FCResourceTypeEnum.FOLDER.equals(this.getType())) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 创建文件夹<br/>
     * <功能详细描述>
     * @throws FileSystemException [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    void createFolder() throws FileContextResourceException;
    
    /**
     * 判断文件定义是否存在<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    boolean exists() throws FileContextResourceException;
    
    /**
     * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
     * The difference between File.delete() and this method are:
     * <ul>
     * <li>A directory to be deleted does not have to be empty.</li>
     * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
     * </ul>
     * <功能详细描述>
     * @param file
     * @return [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    void deleteQuietly() throws FileContextResourceException;
    
    /**
     * 删除文件<br/>
     *    当文件不存在或者文件为文件夹并且不为空时，什么都不做。
     *    不会删除自己子级文件。
     *    如果需要删除子级文件使用delete(FCResourceFilter) 或方法deleteAll();
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return boolean [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    void delete() throws FileContextResourceException;
    
    /**
     * Lists the children of this file.
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return FCResource[] [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    List<FCResource> getChildren() throws FileContextResourceException;
    
    /**
     * 遍历文件资源<br/>
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return List<FileResource> [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default List<FCResource> findFiles() throws FileContextResourceException {
        return findFiles(FCResourceFilterUtils.trueFileFilter(), true);
    }
    
    /**
     * 遍历文件资源<br/>
     * <功能详细描述>
     * @param filter
     * @return [参数说明]
     * 
     * @return List<FileResource> [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    List<FCResource> findFiles(FCResourceFilter fileFilter, boolean depthwise)
            throws FileContextResourceException;
    
    /**
     * Gets the size of the file, in bytes.
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return long [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    long getSize() throws FileContextResourceException;
    
    /**
     * 获取输入流<br/>
     * Gets an input stream for reading the file's content.
     * There may only be a single input or output stream open for the file at any time.
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return InputStream [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    InputStream getInputStream() throws FileContextResourceException;
    
    /**
     * 获取文件的输入流<br/>
     * <功能详细描述>
     * @param bufferSize
     * @return
     * @throws IOException [参数说明]
     * 
     * @return InputStream [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default InputStream getInputStream(final int bufferSize)
            throws FileContextResourceException {
        return getInputStream();
    }
    
    /**
     * Returns the content of a file as a byte array.
     *
     * @return The content as a byte array.
     * @throws IOException if the file content cannot be accessed.
     * @since 2.4
     */
    default byte[] getByteArray() throws FileContextResourceException {
        final long sizeL = getSize();
        if (sizeL > Integer.MAX_VALUE) {
            throw new IllegalStateException(String.format(
                    "File content is too large for a byte array: %,d",
                    sizeL));
        }
        
        final boolean sizeUndefined = sizeL < 0;
        final int size = sizeUndefined ? DEFAULT_BUFFER_SIZE : (int) sizeL;
        final byte[] buf = new byte[size];
        int pos;
        try (final InputStream in = getInputStream(size)) {
            int read = 0;
            for (pos = 0; pos < size && read >= 0; pos += read) {
                read = in.read(buf, pos, size - pos);
            }
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "FCResource.getByteArray() error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
        return sizeUndefined && pos < buf.length ? Arrays.copyOf(buf, ++pos)
                : buf;
    }
    
    /**
     * 获取读取通道<br/>
     * <功能详细描述>
     * @return
     * @throws IOException [参数说明]
     * 
     * @return ReadableByteChannel [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default ReadableByteChannel readableChannel()
            throws FileContextResourceException {
        return Channels.newChannel(getInputStream());
    }
    
    /**
     * Gets an output stream for writing the file's content.
     * <p>
     * If the file does not exist, this method creates it, and the parent folder, if necessary. If the file does exist,
     * it is replaced with whatever is written to the output stream.
     * </p>
     * <p>
     * There may only be a single input or output stream open for the file at any time.
     * </p>
     *
     * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
     *         in a {@code BufferedOutputStream}.
     * @throws FileSystemException If the file is read-only, or is being read, or is being written, or on error opening
     *         the stream.
     */
    OutputStream getOutputStream() throws FileContextResourceException;
    
    /**
     * Gets an output stream for writing the file's content.
     * <p>
     * If the file does not exist, this method creates it, and the parent folder, if necessary. If the file does exist,
     * it is replaced with whatever is written to the output stream.
     * </p>
     * <p>
     * There may only be a single input or output stream open for the file at any time.
     * </p>
     *
     * @param bAppend true if you would like to append to the file. This may not be supported by all implementations.
     * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
     *         in a {@code BufferedOutputStream}.
     * @throws FileSystemException If the file is read-only, or is being read, or is being written, or bAppend is true
     *         and the implementation does not support it, or on error opening the stream.
     */
    OutputStream getOutputStream(boolean bAppend)
            throws FileContextResourceException;
    
    /**
     * Gets an output stream for writing the file's content.
     * <p>
     * If the file does not exist, this method creates it, and the parent folder, if necessary. If the file does exist,
     * it is replaced with whatever is written to the output stream.
     * </p>
     * <p>
     * There may only be a single input or output stream open for the file at any time.
     * </p>
     *
     * @param bufferSize The buffer size to use.
     * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
     *         in a {@code BufferedOutputStream}.
     * @throws FileSystemException If the file is read-only, or is being read, or is being written, or bAppend is true
     *         and the implementation does not support it, or on error opening the stream.
     */
    default OutputStream getOutputStream(final int bufferSize)
            throws FileContextResourceException {
        return getOutputStream();
    }
    
    /**
     * Gets an output stream for writing the file's content.
     * <p>
     * If the file does not exist, this method creates it, and the parent folder, if necessary. If the file does exist,
     * it is replaced with whatever is written to the output stream.
     * </p>
     * <p>
     * There may only be a single input or output stream open for the file at any time.
     * </p>
     *
     * @param bAppend true if you would like to append to the file. This may not be supported by all implementations.
     * @param bufferSize The buffer size to use.
     * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
     *         in a {@code BufferedOutputStream}.
     * @throws FileSystemException If the file is read-only, or is being read, or is being written, or bAppend is true
     *         and the implementation does not support it, or on error opening the stream.
     */
    default OutputStream getOutputStream(final boolean bAppend,
            final int bufferSize) throws FileContextResourceException {
        return getOutputStream(bAppend);
    }
    
    /**
     * Returns the content of a file as a String.
     *
     * @param charset The file character set, may be null.
     * @return The content as a byte array.
     * @throws IOException if the file content cannot be accessed.
     * @since 2.4
     */
    default String getString(final Charset charset)
            throws FileContextResourceException {
        if (getSize() > 0) {
            return new String(getByteArray(),
                    charset == null ? Charset.defaultCharset() : charset);
        } else {
            try (InputStream is = getInputStream()) {
                return IOUtils.toString(is, charset);
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getString(Charset) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
    }
    
    /**
     * Gets the content of a file as a String.
     *
     * @param charset The file character set, may be null.
     * @return The content as a byte array.
     * @throws IOException if the file content cannot be accessed.
     */
    default String getString(final String charset)
            throws FileContextResourceException {
        if (getSize() > 0) {
            try {
                return new String(getByteArray(),
                        charset == null ? Charset.defaultCharset().name()
                                : charset);
            } catch (UnsupportedEncodingException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getString(String) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        } else {
            try (InputStream is = getInputStream()) {
                return IOUtils.toString(is, charset);
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getString(String) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
    }
    
    /**
     * Writes this content to another FileObject.
     *
     * @param file The target FileObject.
     * @throws IOException if an error occurs writing the content.
     * @return the total number of bytes written
     * @since 2.1
     */
    default long write(FCResource file) throws FileContextResourceException {
        try (OutputStream output = file.getOutputStream()) {
            return this.write(output);
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "FCResource.write(FCResource) error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
    }
    
    /**
     * Writes this content to an OutputStream.
     *
     * @param output The target OutputStream.
     * @return the total number of bytes written
     * @throws IOException if an error occurs writing the content.
     */
    default long write(OutputStream output)
            throws FileContextResourceException {
        return write(output, WRITE_BUFFER_SIZE);
    }
    
    /**
     * Writes this content to an OutputStream.
     *
     * @param output The target OutputStream.
     * @param bufferSize The buffer size to write data chunks.
     * @return the total number of bytes written
     * @throws IOException if an error occurs writing the file.
     */
    default long write(OutputStream output, int bufferSize)
            throws FileContextResourceException {
        long count = 0;
        try (InputStream input = getInputStream()) {
            // This read/write code from Apache Commons IO
            final byte[] buffer = new byte[bufferSize];
            int n;
            while (-1 != (n = input.read(buffer))) {
                output.write(buffer, 0, n);
                count += n;
            }
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "FCResource.write(OutputStream,int) error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
        return count;
    }
    
    /**
     * 存储输入流,如果资源已经存在，则覆盖已存在的资源<br/>
     * <功能详细描述>
     * @param inputStream [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    void save(InputStream inputStream) throws FileContextResourceException;
    
    /**
     * 存储输入流,如果资源已经存在，则覆盖已存在的资源<br/>
     * <功能详细描述>
     * @param inputStream [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default void save(MultipartFile file) throws FileContextResourceException {
        AssertUtils.notNull(file, "file is null.");
        
        try (InputStream inputStream = file.getInputStream()) {
            save(inputStream);
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "MultipartFile.getInputStream() error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
    }
    
    /**
     * 存储输入流,如果资源已经存在，则覆盖已存在的资源<br/>
     * <功能详细描述>
     * @param inputStream [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default void save(File file) throws FileContextResourceException {
        AssertUtils.notNull(file, "file is null.");
        AssertUtils.isTrue(file.exists(), "file is not exist.");
        
        try (FileInputStream inputStream = new FileInputStream(file);) {
            save(inputStream);
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "FileInputStream create error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
    }
    
    /**
     * 新增保存资源，如果资源已经存在，则抛出资源已经存在的异常<br/>
     * <功能详细描述>
     * @param inputStream [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    void add(InputStream inputStream) throws FileContextResourceException;
    
    /**
     * 新增保存资源，如果资源已经存在，则抛出资源已经存在的异常<br/>
     * <功能详细描述>
     * @param inputStream [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default void add(MultipartFile file) throws FileContextResourceException {
        AssertUtils.notNull(file, "file is null.");
        
        try (InputStream inputStream = file.getInputStream()) {
            add(inputStream);
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "MultipartFile.getInputStream() error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
    }
    
    /**
     * 新增保存资源，如果资源已经存在，则抛出资源已经存在的异常<br/>
     * <功能详细描述>
     * @param inputStream [参数说明]
     * 
     * @return void [返回类型说明]
     * @exception throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     */
    default void add(File file) throws FileContextResourceException {
        AssertUtils.notNull(file, "file is null.");
        AssertUtils.isTrue(file.exists(), "file is not exist.");
        
        try (FileInputStream inputStream = new FileInputStream(file);) {
            add(inputStream);
        } catch (IOException e) {
            throw new FileContextResourceException(MessageUtils.format(
                    "FileInputStream create error. catalog:{},path:{}.",
                    getCatalog(),
                    getRelativePath()), e);
        }
    }
}
